package com.joeyzh.imagepicker.handwritingboard;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;

import com.joeyzh.imagepicker.R;

import java.util.ArrayList;
import java.util.List;


public class SignatureView extends View {

	private Context mContext;
	/**
	 * 手写画笔
	 */
	private Paint mGesturePaint = new Paint();


	/**
	 * 画笔宽度 px；
	 */
	private int mPaintWidth = 10;
	/**
	 * 前景色
	 */
	private int mPenColor = Color.BLACK;
	/**
	 * 背景色（指最终签名结果文件的背景颜色，默认为透明色）
	 */
	private int mBackColor = Color.TRANSPARENT;


	private List<TimedPoint> mPoints;
	private boolean mIsEmpty;
	private float mLastTouchX;
	private float mLastTouchY;
	private float mLastVelocity;
	private float mLastWidth;
	private RectF mDirtyRect;

	private int mMinWidth;
	private int mMaxWidth;
	private float mVelocityFilterWeight;
	private OnSignedListener mOnSignedListener;

	private Path mPath = new Path();
	
	private Bitmap mSignatureBitmap = null;
	private Canvas mSignatureBitmapCanvas = null;

	public SignatureView(Context context) {
		super(context);
		init(context);
	}

	public SignatureView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context, attrs);
	}

	public SignatureView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init(context, attrs);
	}

	public void init(Context context, AttributeSet attrs) {
		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SignatureView, 0, 0);

		try {
			mMinWidth = a.getDimensionPixelSize(R.styleable.SignatureView_minWidth, convertDpToPx(3));
			mMaxWidth = a.getDimensionPixelSize(R.styleable.SignatureView_maxWidth, convertDpToPx(20));
			mGesturePaint.setColor(a.getColor(R.styleable.SignatureView_penColor, Color.BLACK));
		} finally {
			a.recycle();
		}
		init(context);
	}

	public void init(Context context) {
		this.mContext = context;
		//设置抗锯齿
		mGesturePaint.setAntiAlias(true);
		//设置签名笔画样式
		mGesturePaint.setStyle(Paint.Style.STROKE);
		//设置笔画宽度
		mGesturePaint.setStrokeWidth(mPaintWidth);
		//设置签名颜色
		mGesturePaint.setColor(mPenColor);
		//画笔末端样式
		mGesturePaint.setStrokeCap(Paint.Cap.ROUND);
		mGesturePaint.setStrokeJoin(Paint.Join.ROUND);
		mDirtyRect = new RectF();
		clear();
	}

	/**
	 * Set the pen color from a given resource. If the resource is not found,
	 * {@link Color#BLACK} is assumed.
	 *
	 * @param colorRes
	 *            the color resource.
	 */
	public void setPenColorRes(int colorRes) {
		try {
			setPenColor(getResources().getColor(colorRes));
		} catch (Resources.NotFoundException ex) {
			setPenColor(getResources().getColor(Color.BLACK));
		}
	}

	/**
	 * Set the pen color from a given color.
	 *
	 * @param color
	 *            the color.
	 */
	public void setPenColor(int color) {
		mGesturePaint.setColor(color);
	}

	/**
	 * Set the minimum width of the stroke in pixel.
	 *
	 * @param minWidth
	 *            the width in dp.
	 */
	public void setMinWidth(float minWidth) {
		mMinWidth = convertDpToPx(minWidth);
	}

	/**
	 * Set the maximum width of the stroke in pixel.
	 *
	 * @param maxWidth
	 *            the width in dp.
	 */
	public void setMaxWidth(float maxWidth) {
		mMaxWidth = convertDpToPx(maxWidth);
	}

	/**
	 * Set the velocity filter weight.
	 *
	 * @param velocityFilterWeight
	 *            the weight.
	 */
	public void setVelocityFilterWeight(float velocityFilterWeight) {
		mVelocityFilterWeight = velocityFilterWeight;
	}

	public void clear() {
		mPoints = new ArrayList<TimedPoint>();
		mLastVelocity = 0;
		mLastWidth = (mMinWidth + mMaxWidth) / 2;
		mPath.reset();

		if (mSignatureBitmap != null) {
			mSignatureBitmap = null;
			ensureSignatureBitmap();
		}

		setIsEmpty(true);

		invalidate();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (!isEnabled())
			return false;

		float eventX = event.getX();
		float eventY = event.getY();

		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				getParent().requestDisallowInterceptTouchEvent(true);
				mPoints.clear();
				mPath.moveTo(eventX, eventY);
				mLastTouchX = eventX;
				mLastTouchY = eventY;
				addPoint(new TimedPoint(eventX, eventY));

			case MotionEvent.ACTION_MOVE:
				resetDirtyRect(eventX, eventY);
				addPoint(new TimedPoint(eventX, eventY));
				break;

			case MotionEvent.ACTION_UP:
				resetDirtyRect(eventX, eventY);
				addPoint(new TimedPoint(eventX, eventY));
				getParent().requestDisallowInterceptTouchEvent(true);
				setIsEmpty(false);
				break;

			default:
				return false;
		}

		// invalidate();
		invalidate((int) (mDirtyRect.left - mMaxWidth), (int) (mDirtyRect.top - mMaxWidth),
				(int) (mDirtyRect.right + mMaxWidth), (int) (mDirtyRect.bottom + mMaxWidth));

		return true;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (mSignatureBitmap != null) {
			canvas.drawBitmap(mSignatureBitmap, 0, 0, mGesturePaint);
		}
	}

	public void setOnSignedListener(OnSignedListener listener) {
		mOnSignedListener = listener;
	}

	public boolean isEmpty() {
		return mIsEmpty;
	}

	public Bitmap getSignatureBitmap() {
		Bitmap originalBitmap = getTransparentSignatureBitmap();
		Bitmap whiteBgBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(),
				Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(whiteBgBitmap);
		canvas.drawColor(Color.WHITE);
		canvas.drawBitmap(originalBitmap, 0, 0, null);
		return whiteBgBitmap;
	}

	public void setSignatureBitmap(Bitmap signature) {
		clear();
		ensureSignatureBitmap();

		RectF tempSrc = new RectF();
		RectF tempDst = new RectF();

		int dWidth = signature.getWidth();
		int dHeight = signature.getHeight();
		int vWidth = getWidth();
		int vHeight = getHeight();

		// Generate the required transform.
		tempSrc.set(0, 0, dWidth, dHeight);
		tempDst.set(0, 0, vWidth, vHeight);

		Matrix drawMatrix = new Matrix();
		drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);

		Canvas canvas = new Canvas(mSignatureBitmap);
		canvas.drawBitmap(signature, drawMatrix, null);
		setIsEmpty(false);
		invalidate();
	}

	public Bitmap getTransparentSignatureBitmap() {
		ensureSignatureBitmap();
		return mSignatureBitmap;
	}

	public Bitmap getTransparentSignatureBitmap(boolean trimBlankSpace) {

		if (!trimBlankSpace) {
			return getTransparentSignatureBitmap();
		}

		ensureSignatureBitmap();

		int imgHeight = mSignatureBitmap.getHeight();
		int imgWidth = mSignatureBitmap.getWidth();

		int backgroundColor = Color.TRANSPARENT;

		int xMin = Integer.MAX_VALUE, xMax = Integer.MIN_VALUE, yMin = Integer.MAX_VALUE, yMax = Integer.MIN_VALUE;

		boolean foundPixel = false;

		// Find xMin
		for (int x = 0; x < imgWidth; x++) {
			boolean stop = false;
			for (int y = 0; y < imgHeight; y++) {
				if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
					xMin = x;
					stop = true;
					foundPixel = true;
					break;
				}
			}
			if (stop)
				break;
		}

		// Image is empty...
		if (!foundPixel)
			return null;

		// Find yMin
		for (int y = 0; y < imgHeight; y++) {
			boolean stop = false;
			for (int x = xMin; x < imgWidth; x++) {
				if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
					yMin = y;
					stop = true;
					break;
				}
			}
			if (stop)
				break;
		}

		// Find xMax
		for (int x = imgWidth - 1; x >= xMin; x--) {
			boolean stop = false;
			for (int y = yMin; y < imgHeight; y++) {
				if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
					xMax = x;
					stop = true;
					break;
				}
			}
			if (stop)
				break;
		}

		// Find yMax
		for (int y = imgHeight - 1; y >= yMin; y--) {
			boolean stop = false;
			for (int x = xMin; x <= xMax; x++) {
				if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
					yMax = y;
					stop = true;
					break;
				}
			}
			if (stop)
				break;
		}

		return Bitmap.createBitmap(mSignatureBitmap, xMin, yMin, xMax - xMin, yMax - yMin);
	}

	private void addPoint(TimedPoint newPoint) {
		mPoints.add(newPoint);
		if (mPoints.size() > 2) {
			// To reduce the initial lag make it work with 3 mPoints
			// by copying the first point to the beginning.
			if (mPoints.size() == 3)
				mPoints.add(0, mPoints.get(0));

			ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2));
			TimedPoint c2 = tmp.c2;
			tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3));
			TimedPoint c3 = tmp.c1;
			Bezier curve = new Bezier(mPoints.get(1), c2, c3, mPoints.get(2));

			TimedPoint startPoint = curve.startPoint;
			TimedPoint endPoint = curve.endPoint;

			float velocity = endPoint.velocityFrom(startPoint);
			velocity = Float.isNaN(velocity) ? 0.0f : velocity;

			velocity = mVelocityFilterWeight * velocity + (1 - mVelocityFilterWeight) * mLastVelocity;

			// The new width is a function of the velocity. Higher velocities
			// correspond to thinner strokes.
			float newWidth = strokeWidth(velocity);

			// The Bezier's width starts out as last curve's final width, and
			// gradually changes to the stroke width just calculated. The new
			// width calculation is based on the velocity between the Bezier's
			// start and end mPoints.
			addBezier(curve, mLastWidth, newWidth);

			mLastVelocity = velocity;
			mLastWidth = newWidth;

			// Remove the first element from the list,
			// so that we always have no more than 4 mPoints in mPoints array.
			mPoints.remove(0);
		}
	}

	private void addBezier(Bezier curve, float startWidth, float endWidth) {
		ensureSignatureBitmap();
		float originalWidth = mGesturePaint.getStrokeWidth();
		float widthDelta = endWidth - startWidth;
		float drawSteps = (float) Math.floor(curve.length());

		for (int i = 0; i < drawSteps; i++) {
			// Calculate the Bezier (x, y) coordinate for this step.
			float t = ((float) i) / drawSteps;
			float tt = t * t;
			float ttt = tt * t;
			float u = 1 - t;
			float uu = u * u;
			float uuu = uu * u;

			float x = uuu * curve.startPoint.x;
			x += 3 * uu * t * curve.control1.x;
			x += 3 * u * tt * curve.control2.x;
			x += ttt * curve.endPoint.x;

			float y = uuu * curve.startPoint.y;
			y += 3 * uu * t * curve.control1.y;
			y += 3 * u * tt * curve.control2.y;
			y += ttt * curve.endPoint.y;

			// Set the incremental stroke width and draw.
			//mGesturePaint.setStrokeWidth(startWidth + ttt * widthDelta);
			mSignatureBitmapCanvas.drawPoint(x, y, mGesturePaint);
			expandDirtyRect(x, y);
		}

		mGesturePaint.setStrokeWidth(originalWidth);
	}

	private ControlTimedPoints calculateCurveControlPoints(TimedPoint s1, TimedPoint s2, TimedPoint s3) {
		float dx1 = s1.x - s2.x;
		float dy1 = s1.y - s2.y;
		float dx2 = s2.x - s3.x;
		float dy2 = s2.y - s3.y;

		TimedPoint m1 = new TimedPoint((s1.x + s2.x) / 2.0f, (s1.y + s2.y) / 2.0f);
		TimedPoint m2 = new TimedPoint((s2.x + s3.x) / 2.0f, (s2.y + s3.y) / 2.0f);

		float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1);
		float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2);

		float dxm = (m1.x - m2.x);
		float dym = (m1.y - m2.y);
		float k = l2 / (l1 + l2);
		TimedPoint cm = new TimedPoint(m2.x + dxm * k, m2.y + dym * k);

		float tx = s2.x - cm.x;
		float ty = s2.y - cm.y;

		return new ControlTimedPoints(new TimedPoint(m1.x + tx, m1.y + ty), new TimedPoint(m2.x + tx, m2.y + ty));
	}

	private float strokeWidth(float velocity) {
		return Math.max(mMaxWidth / (velocity + 1), mMinWidth);
	}

	/**
	 * Called when replaying history to ensure the dirty region includes all
	 * mPoints.
	 *
	 * @param historicalX
	 *            the previous x coordinate.
	 * @param historicalY
	 *            the previous y coordinate.
	 */
	private void expandDirtyRect(float historicalX, float historicalY) {
		if (historicalX < mDirtyRect.left) {
			mDirtyRect.left = historicalX;
		} else if (historicalX > mDirtyRect.right) {
			mDirtyRect.right = historicalX;
		}
		if (historicalY < mDirtyRect.top) {
			mDirtyRect.top = historicalY;
		} else if (historicalY > mDirtyRect.bottom) {
			mDirtyRect.bottom = historicalY;
		}
	}

	/**
	 * Resets the dirty region when the motion event occurs.
	 *
	 * @param eventX
	 *            the event x coordinate.
	 * @param eventY
	 *            the event y coordinate.
	 */
	private void resetDirtyRect(float eventX, float eventY) {

		// The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion
		// event occurred.
		mDirtyRect.left = Math.min(mLastTouchX, eventX);
		mDirtyRect.right = Math.max(mLastTouchX, eventX);
		mDirtyRect.top = Math.min(mLastTouchY, eventY);
		mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
	}

	private void setIsEmpty(boolean newValue) {
		mIsEmpty = newValue;
		if (mOnSignedListener != null) {
			if (mIsEmpty) {
				mOnSignedListener.onClear();
			} else {
				mOnSignedListener.onSigned();
			}
		}
	}

	private void ensureSignatureBitmap() {
		if (mSignatureBitmap == null) {
			mSignatureBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
			mSignatureBitmapCanvas = new Canvas(mSignatureBitmap);
		}
	}

	private int convertDpToPx(float dp) {
		return Math.round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
	}



	public interface OnSignedListener {
		public void onSigned();

		public void onClear();
	}
}
